
#include "lg.h"
#include "lg_window.h"

#ifdef LG_WINDOWS

#include <Windows.h>
#include <windowsx.h>
#include <stdio.h>

#include "GL/glew.h"

#define LGOGL_CORE

#include "lgogl_canvas.h"
#include "lgogl_shader.h"
#include "lgogl_winpc.h"
#include "lgogl_vertex.h"


typedef struct {
    LGWindow wind;
    HWND hwnd;
    HDC hdc;
    LGWindow* next;
}OglWindow;

typedef struct {
    LGVector position;
    LGVector uv;
}WindowVertex;

static struct {
    HGLRC hglrc;
    LGVertex* vertex;
    OglWindow* root;
    LGMaterial* material;
    unsigned uniformIndex;
}ctx = { 0 };

WindowVertex wvs[] = {
    { -1.f, -1.f, 0.f, 1.f,       0.f, 0.f, 0.f, 1.f},
    { -1.f, 1.f, 0.f, 1.f,        0.f, 1.f, 0.f, 1.f},
    { 1.f, -1.f, 0.f, 1.f,        1.f, 0.f, 0.f, 1.f},
    { 1.f, 1.f, 0.f, 1.f,         1.f, 1.f, 0.f, 1.f},
};
unsigned indexs[] = { 0, 1, 2, 1, 2, 3, };

static OglWindow*
findWindow(HWND hwnd){
    OglWindow* next = ctx.root;
    while (next) {
        if (next->hwnd == hwnd) {
            return next;
        }
        next = (OglWindow*)next->next;
    }
    return NULL;
}

static LRESULT CALLBACK
wndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    OglWindow* oglWin = findWindow(hwnd);
    if (!oglWin) {
        return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
    LGWindow* win = (LGWindow*)oglWin;
    switch (uMsg) {
    case WM_CLOSE:
        if (win->onEvt) {
            win->onEvt(win, LG_WINDOW_EVT_CLOSE);
        }
        break;
    case WM_MOUSEMOVE: {
        int keyType = (int)wParam;
        int xpos = GET_X_LPARAM(lParam);
        int ypos = GET_Y_LPARAM(lParam);
        printf("mouse mvoe:%d %d %d\n", keyType, xpos, ypos);
        win->mousePosition[0] = xpos;
        win->mousePosition[1] = ypos;
        if (win->onEvt) {
            win->onEvt(win, LG_WINDOW_EVT_MOUSE_MOVE);
        }
        break;
    }
    case WM_CREATE: {
        LPCREATESTRUCT st = (LPCREATESTRUCT)lParam;
        OglWindow* oglW = (OglWindow*)st->lpCreateParams;
        break;
    }
    case WM_SIZE: {
        int width = LOWORD(lParam);
        int height = HIWORD(lParam);
        win->width = width;
        win->height = height;
        if (win->onEvt) {
            win->onEvt(win, LG_WINDOW_EVT_RESIZE);
        }
        break;
    }
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

static int
initHdc(HDC hdc) {
    PIXELFORMATDESCRIPTOR pfd = {         /**< 设置像素描述结构 */
       sizeof(PIXELFORMATDESCRIPTOR),    /**< 像素描述结构的大小 */
       1,                                /**< 版本号 */
       PFD_DRAW_TO_WINDOW |              /**< 缓存区的输出显示在一个窗口中 */
       PFD_SUPPORT_OPENGL |              /**< 缓存区支持OpenGL绘图 */
       PFD_STEREO |                      /**< 颜色缓存区是立体缓存 */
       PFD_DOUBLEBUFFER,                 /**< 颜色缓存区是双缓存 */
       PFD_TYPE_RGBA,                    /**< 使用RGBA颜色格式 */
       16,                               /**< 颜色缓存区中颜色值所占的位深 */
       0, 0, 0, 0, 0, 0,                 /**< 使用默认的颜色设置 */
       0,                                /**< 无Alpha缓存 */
       0,                                /**< 颜色缓存区中alpha成分的移位计数 */
       0,                                /**< 无累计缓存区 */
       0, 0, 0, 0,                       /**< 累计缓存区无移位 */
       32,                               /**< 32位深度缓存 */
       0,                                /**< 无蒙版缓存 */
       0,                                /**< 无辅助缓存区 */
       PFD_MAIN_PLANE,                   /**< 必须为PFD_MAIN_PLANE，设置为主绘图层 */
       0,                                /**< 表示OpenGL实现所支持的上层或下层平面的数量 */
       0, 0, 0                           /**< 过时，已不再使用 */
    };
    int pixelFormat = ChoosePixelFormat(hdc, &pfd);
    if (!pixelFormat) {
        return 1;
    }
    if (!SetPixelFormat(hdc, pixelFormat, &pfd)) {
        return 1;
    }
    return 0;
}

static int
initContext() {
    DWORD style = WS_TILEDWINDOW;
    DWORD exStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
    int sysWidth = GetSystemMetrics(SM_CXSCREEN);
    int sysHeight = GetSystemMetrics(SM_CYSCREEN);
    RECT rect = { 0, 0, 400, 400 };
    AdjustWindowRectEx(&rect, style, 0, exStyle);
    HWND hwnd = CreateWindowEx(exStyle,
        "LandGraphicsOgl", "test", style,
        0, 0, rect.right - rect.left, rect.bottom - rect.top,
        HWND_DESKTOP, 0, GetModuleHandleW(NULL), NULL);
    if (!hwnd)
        return 1;
    HDC hdc = GetDC(hwnd);
    if (!hdc) {
        DestroyWindow(hwnd);
        return 1;
    }
    if (initHdc(hdc)) {
        DestroyWindow(hwnd);
        return 1;
    }
    ctx.hglrc = wglCreateContext(hdc);
    if (!ctx.hglrc) {
        DestroyWindow(hwnd);
        return 1;
    }
    wglMakeCurrent(hdc, ctx.hglrc);
    if (glewInit() != GLEW_OK) {
        DestroyWindow(hwnd);
        return 1;
    }
    //DestroyWindow(hwnd);
    if (!ctx.vertex) {
        LGVertexAttr attr[2] = {
            {LG_VERTEX_POSITION, LG_VERTEX_OFFSET(WindowVertex, position)},
            {LG_VERTEX_UV, LG_VERTEX_OFFSET(WindowVertex, uv)},
        };
        ctx.vertex = lgNewVertex(attr, 2, sizeof(WindowVertex), 16);
        lgRetainRef(ctx.vertex);
        lgPushVertexData(ctx.vertex, wvs, 4);
        lgPushVertexIndexs(ctx.vertex, indexs, 6);
        lgUploadVertex(ctx.vertex);
    }
    return 0;
}

static int
makeCurrent(LGWindow* window) {
    OglWindow* oglWin = (OglWindow*)window;
    if (!wglMakeCurrent(oglWin->hdc, ctx.hglrc)) {
        return 1;
    }
    return 0;
}

static size_t
sizeofWindow() {
    return sizeof(OglWindow);
}

static int
openWindow(LGWindow* window) {
    OglWindow* oglWin = (OglWindow*)window;
    oglWin->next = NULL;
    DWORD style = WS_TILEDWINDOW;
    DWORD exStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
    int sysWidth = GetSystemMetrics(SM_CXSCREEN);
    int sysHeight = GetSystemMetrics(SM_CYSCREEN);
    int nX = (sysWidth - window->style.width) / 2;
    int nY = (sysHeight - window->style.height) / 2;
    RECT rect = { 0, 0, window->style.width, window->style.height };
    AdjustWindowRectEx(&rect, style, 0, exStyle);
    HWND hwnd = CreateWindowEx(exStyle,
        "LandGraphicsOgl", "test", style,
        nX, nY, rect.right - rect.left, rect.bottom - rect.top,
        HWND_DESKTOP, 0, GetModuleHandleW(NULL), window);
    if (!hwnd)
        return 1;
    HDC hdc = GetDC(hwnd);
    if (!hdc) {
        DestroyWindow(hwnd);
        return 1;
    }
    if (initHdc(hdc)) {
        DestroyWindow(hwnd);
        return 1;
    }
    oglWin->hwnd = hwnd;
    oglWin->hdc = hdc;
    ShowWindow(hwnd, SW_NORMAL);
    wglMakeCurrent(hdc, ctx.hglrc);
    if (!ctx.root) {
        ctx.root = oglWin;
    }
    else {
        ctx.root->next = window;
    }
    return 0;
}

static int
closeWindow_(LGWindow* window) {
    OglWindow* oglWin = (OglWindow*)window;
    CloseWindow(oglWin->hwnd);
    DestroyWindow(oglWin->hwnd);
    return 0;
}

static int
setParent(LGWindow* window, LGWindow* parent) {
    OglWindow* oglWin = (OglWindow*)window;
    OglWindow* oglParent= (OglWindow*)parent;
    SetParent(oglWin->hwnd, oglParent->hwnd);
    return 0;
}

static int
drawCanvas(LGWindow* window, void* canvas) {
    return 0;
}



static int
renderWindow(LGWindow* window, LGCanvas* canvas) {
    OglWindow* oglWin = (OglWindow*)window;
    LGOGLCanvas* oglCanvas = (LGOGLCanvas*)canvas;
    makeCurrent(window);
    lgoglBindCanvas(NULL);
    glViewport(0, 0, window->width, window->height);
    glDepthMask(GL_TRUE);
    glViewport(0, 0, window->width, window->height);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    if (!ctx.material) {
        LGShader* shader = lgNewStdShader(LGSTD_BASE_TEXTURE_SHADER);
        ctx.material = lgNewMaterial(shader);
        lgRetainRef(ctx.material);
        lgSetDepthTest(ctx.material, LG_DEPTH_TEST_ALWAYS);
        ctx.uniformIndex = lgRegUniformCanvas(ctx.material, "lgBaseTexture", canvas);
    }

    lgSetUniformCanvas(ctx.material, ctx.uniformIndex, canvas);
    lgoglBindMaterial(ctx.material);
    lgoglBindVertex(ctx.vertex);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glBlendEquation(GL_FUNC_ADD);
    //glDisable(GL_DEPTH_TEST);
    //glEnable(GL_DEPTH_TEST);
    //glDepthFunc(GL_GREATER);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL);
    GLint err = glGetError();
    if (err) {
        printf("renderWindow err:%d\n", err);
    }
    SwapBuffers(oglWin->hdc);
    return 0;
}


static LGWindowBackend windowBackend = {
    sizeofWindow,
    openWindow,
    closeWindow_,
    setParent,
    drawCanvas,
    renderWindow,
};

LGOGL_API LGWindowBackend*
lgoglWinpcWindowBackend() {
    return &windowBackend;
}

LGOGL_API int
lgoglInitWinpcContext(LGConfig* config, LGOGLConfig* oglConfig) {
    WNDCLASSEX wc;
    memset(&wc, 0, sizeof(WNDCLASSEX));
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    wc.lpfnWndProc = wndProc;
    wc.hInstance = GetModuleHandleW(NULL);
    wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = "LandGraphicsOgl";
    RegisterClassEx(&wc);

    return initContext();
}

LGOGL_API int
lgoglDispatchWinpc() {
    MSG msg;
    if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE) != 0) {
        if (msg.message != WM_QUIT) {
            DispatchMessage(&msg);
        }
        else {
            ExitThread(0);
        }
    }
    return 0;
}

LGOGL_API void
lgoglClearWindow(LGWindow* window, LGVector* color) {
    makeCurrent(window);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glViewport(0, 0, window->width, window->height);
    glClearColor(color->x, color->y, color->z, color->w);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
}

LGOGL_API void
lgoglBindWindow(LGWindow* window) {
    makeCurrent(window);
}

#endif